package com.jprocms.module.cms.controller.directive;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.jpro.framework.common.pojo.PageResult;
import com.jprocms.module.cms.config.JproProperties;
import com.jprocms.module.cms.controller.admin.content.vo.ContentFrontQueryReqVO;
import com.jprocms.module.cms.controller.admin.content.vo.JoinTable;
import com.jprocms.module.cms.dal.dataobject.channel.ChannelDO;
import com.jprocms.module.cms.dal.dataobject.content.ContentDO;
import com.jprocms.module.cms.enums.EnumContentStatus;
import com.jprocms.module.cms.es.enums.EnumContentSort;
import com.jprocms.module.cms.query.CustomFieldQuery;
import com.jprocms.module.cms.query.WhereCondition;
import com.jprocms.module.cms.service.channel.ChannelService;
import com.jprocms.module.cms.service.content.ContentService;
import com.jprocms.module.cms.util.Freemarkers;
import com.jprocms.module.cms.util.FrontContextUtil;
import com.jprocms.module.cms.web.Directives;
import freemarker.core.Environment;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.lang.Nullable;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

import static com.jprocms.module.cms.util.Freemarkers.ORDER_TABLE_CONTENT_COUNT;
import static com.jprocms.module.cms.util.Freemarkers.ORDER_TABLE_SPILIT;
import static com.jprocms.module.cms.web.Directives.*;


/**
 * 内容列表 自定义标签
 *
 * @author jprocms
 */
public class ContentListDirective implements TemplateDirectiveModel {
    /**
     * 是否获取所有站点内容。布尔型(Boolean)。默认：false。
     * 场景：可用于在前台聚合某个栏目跨站群数据
     */
    public static final String ALL_SITE = "allSite";
    /**
     * 站点ID。
     */
    public static final String SITE_ID = "siteId";
    /**
     * 栏目路径。
     */
    public static final String CHANNEL_PATH = "channelPath";
    /**
     * 栏目ID。
     */
    public static final String CHANNEL_ID = "channelId";
    /**
     * TagID。
     */
    public static final String TAG_ID = "tagId";
    /**
     * 开始发布日期。
     */
    public static final String BEGIN_RELEASE_DATE = "beginReleaseTime";
    /**
     * 结束发布日期。
     */
    public static final String END_RELEASE_DATE = "endReleaseTime";
    /**
     * 标题
     */
    public static final String TITLE = "title";
    /**
     * 标题或正文。ES搜索使用
     */
    public static final String Q = "q";
    /**
     * 正文(只在es支持搜索的时候使用)
     */
    public static final String TEXT = "text";
    /**
     * 不包含的ID。整型(Integer)。多个用英文逗号分隔，如'1,2,5'。
     */
    public static final String EXCLUDE_ID = "excludeId";
    /**
     * 是否包含子栏目的内容。布尔型(Boolean)。默认：true。
     */
    public static final String INCLUDE_CHILD_CHANNEL = "includeChildChannel";
    /**
     * 是否置顶
     */
    public static final String TOP_LEVEL = "topLevel";
    /**
     * 标记属性id 来自数据字典 多个用英文逗号分隔，
     */
    public static final String DICT_MARK = "dictMark";
    /**
     * 区块ID 来自数据字典 多个用英文逗号分隔，
     */
    public static final String DICT_BLOCK = "dictBlock";
    /**
     * 排除标记属性id 来自数据字典 多个用英文逗号分隔，
     */
    public static final String EXCLUDE_DICT_MARK = "excludeDictMark";
    /**
     * 排除区块ID 来自数据字典 多个用英文逗号分隔，
     */
    public static final String EXCLUDE_DICT_BLOCK = "excludeDictBlock";
    /**
     * 最大分页数量
     */
    public static final String PAGE_MAX = "pageMax";

    private static Collection<Long> getChannelIds(Map<String, ?> params, @Nullable Long siteId, boolean includeChild, ChannelService channelService) {
        Collection<Long> channelIds = Optional.ofNullable(getLongs(params, CHANNEL_ID)).orElse(Collections.emptyList());
        Collection<String> channelPaths = getStrings(params, CHANNEL_PATH);
        return getChannelIds(channelIds, channelPaths, siteId, includeChild, channelService);
    }

    public static Collection<Long> getChannelIds(Collection<Long> channelIds, Collection<String> channelPaths, @Nullable Long siteId, boolean includeChild, ChannelService channelService) {
        if (CollectionUtils.isEmpty(channelIds)) {
            if (CollectionUtils.isNotEmpty(channelPaths)) {
                channelIds = channelService.selectByChannelPath(channelPaths, siteId).stream().map(ChannelDO::getId).collect(Collectors.toList());
            }
        }
        //包含子栏目则查询遍历所有叶子节点栏目
        //叶子节点超过1000 oracle下in查询有问题 后续完善
        if (includeChild && CollUtil.isNotEmpty(channelIds)) {
            return com.jpro.framework.common.util.collection.CollectionUtils.convertList(channelService.selectLeafChannels(channelIds), ChannelDO::getId);
        }
        return channelIds;
    }


    @Nullable
    public static Collection<JoinTable> getJoinTables(Map<String, ?> params) {
        Collection<String> orderStrs = getStrings(params, ORDER_BY);
        Set<JoinTable> joinTables = new HashSet<>();
        if (CollUtil.isNotEmpty(orderStrs)) {
            orderStrs = orderStrs.stream().filter(c -> StringUtils.isNotBlank(c)).collect(Collectors.toList());
            for (String orderStr : orderStrs) {
                if (orderStr.lastIndexOf(ORDER_TABLE_SPILIT) != -1) {
                    String tableName = orderStr.substring(0, orderStr.lastIndexOf(ORDER_TABLE_SPILIT));
                    if (tableName.contains(ORDER_TABLE_CONTENT_COUNT)) {
                        joinTables.add(new JoinTable("cms_content_count", "count", "id", "id"));
                    }
                }
            }
        }
        return joinTables;
    }

    protected void doExecute(Environment env, Map<String, TemplateModel> params, TemplateModel[] loopVars,
                             TemplateDirectiveBody body, boolean isPage) throws TemplateException, IOException {
        Freemarkers.requireLoopVars(loopVars);
        Freemarkers.requireBody(body);
        ContentFrontQueryReqVO frontQueryReqVO = new ContentFrontQueryReqVO();
        frontQueryReqVO.setQuerySubChannel(jproProperties.isPublishMultiChannel());
        Long defSiteId = FrontContextUtil.getSiteId(env);
        Long siteId = getLong(params, SITE_ID);
        // 不获取所有站点，则给默认站点ID
        if (siteId == null && !getBoolean(params, ALL_SITE, false)) {
            siteId = defSiteId;
        }
        frontQueryReqVO.setSiteId(siteId);
        Boolean includeChildChannel = getBoolean(params, INCLUDE_CHILD_CHANNEL, true);
        Collection<Long> channelIds = getChannelIds(params, siteId, includeChildChannel, channelService);
        frontQueryReqVO.setChannelIds(channelIds);
        frontQueryReqVO.setStatus(EnumContentStatus.PUBLISHED.getStatus());
        Optional.ofNullable(getLongs(params, TAG_ID)).ifPresent(frontQueryReqVO::setTagIds);

        Optional.ofNullable(Directives.getOffsetDateTime(params, BEGIN_RELEASE_DATE)).ifPresent(frontQueryReqVO::toSetPublishBeginDate);
        Optional.ofNullable(Directives.getOffsetDateTime(params, END_RELEASE_DATE)).ifPresent(frontQueryReqVO::toSetPublishEndDate);

        Optional.ofNullable(Directives.getString(params, TITLE)).ifPresent(frontQueryReqVO::setTitle);
        Optional.ofNullable(getLongs(params, EXCLUDE_ID)).ifPresent(frontQueryReqVO::setExcludeIds);
        Optional.ofNullable(getInteger(params, TOP_LEVEL)).ifPresent(frontQueryReqVO::setTopLevel);
        Optional.ofNullable(getLongs(params, DICT_BLOCK)).ifPresent(frontQueryReqVO::setDictBlocks);
        Optional.ofNullable(getLongs(params, DICT_MARK)).ifPresent(frontQueryReqVO::setDictMarks);
        Optional.ofNullable(getLongs(params, EXCLUDE_DICT_BLOCK)).ifPresent(frontQueryReqVO::setExcludeDictBlocks);
        Optional.ofNullable(getLongs(params, EXCLUDE_DICT_MARK)).ifPresent(frontQueryReqVO::setExcludeDictMarks);
        frontQueryReqVO.setPageSize(Directives.getPageSize(params, env));
        //默认置顶优先，发布时间降序排序
        Collection<OrderItem> orderItems = getOrderItems(params, "top_level_desc,release_time_desc");
        frontQueryReqVO.setOrderItems(orderItems);
        frontQueryReqVO.setOrderBy(EnumContentSort.DEFAULT.getSort());
        //es的排序目前一般排序只考虑一个字段
        if (CollUtil.isNotEmpty(orderItems)) {
            frontQueryReqVO.setOrderBy(EnumContentSort.getContentSort(orderItems.iterator().next()).getSort());
        }
        //目前就考虑排序拼接额外连接的表
        frontQueryReqVO.setJoinTables(getJoinTables(params));
        //自定义查询条件
        Map<String, String> customsQueryMap = getCustomsQueryMap(params);
        if (CollUtil.isNotEmpty(customsQueryMap)) {
            List<WhereCondition> customsConditions = CustomFieldQuery.parse(customsQueryMap);
            frontQueryReqVO.setCustomsConditions(customsConditions);
        }
        if (isPage) {
            Integer maxPage = getInteger(params, PAGE_MAX);
            int pageNo = getPage(params, env);
            if (maxPage != null && pageNo > maxPage) {
                pageNo = maxPage;
            }
            frontQueryReqVO.setPageNo(pageNo);
            PageResult<ContentDO> contentPageFront = contentService.getContentPageFront(frontQueryReqVO);
            Directives.setTotalPages(contentPageFront.getPages());
            loopVars[0] = env.getObjectWrapper().wrap(contentPageFront);
        } else {
            frontQueryReqVO.setOffset(Directives.getOffset(params));
            frontQueryReqVO.setCount(Directives.getCount(params));
            List<ContentDO> list = contentService.getContentListFront(frontQueryReqVO);
            loopVars[0] = env.getObjectWrapper().wrap(list);
        }
        body.render(env.getOut());
    }

    @SuppressWarnings("unchecked")
    @Override
    public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
            throws TemplateException, IOException {
        doExecute(env, params, loopVars, body, false);
    }

    private final ContentService contentService;
    private final ChannelService channelService;
    private JproProperties jproProperties;

    public ContentListDirective(ContentService contentService, ChannelService channelService, JproProperties jproProperties) {
        this.contentService = contentService;
        this.channelService = channelService;
        this.jproProperties = jproProperties;
    }
}
